using MongoDB.Driver;
using Sustainalytics.Synchronization.Validation.Internals;
using Sustainalytics.Synchronization.Validation.Validators;
using System;
using System.Collections;
using System.Threading.Tasks;

namespace Sustainalytics.Synchronization.Validation.Handlers.Common
{
    public abstract class Handler<TEntity, TCcompanyIds> : IHandler<TEntity, TEntity>
    {
        protected readonly TCcompanyIds CompaniesIds;
        protected readonly IMongoCollection<TEntity> ReferenceDataCollection;
        protected readonly IMongoCollection<TEntity> ValidatedDataCollection;
        protected readonly Validator<TEntity> Validator;

        protected Handler(
            TCcompanyIds companiesIds,
            ValidationMongoCollection<TEntity> collections,
            Validator<TEntity> validator)
        {
            CompaniesIds = companiesIds;
            ValidatedDataCollection = collections.CurrentCollection;
            ReferenceDataCollection = collections.OldCollection;
            Validator = validator;
        }

        public virtual async Task ForEachElementsAsync(Action<ValidationResult> validationCallback)
        {
            if (Validator.HasAssignedRules)
            {
                foreach (Guid companyId in (IEnumerable)CompaniesIds)
                {
                    await ValidatedDataCollection.Find(GetValidatedEntityFilterDefinition(companyId))
                        .Project<TEntity>(GetProjectionDefinition())
                        .ForEachAsync(Processor(validationCallback))
                        .ConfigureAwait(false);
                }
            }
        }

        public virtual Task<TEntity> GetReferenceElementAsync(TEntity validatedEntity)
            => ReferenceDataCollection.Find(GetReferenceEntityFilter(validatedEntity))
                    .Project<TEntity>(GetProjectionDefinition())
                    .FirstOrDefaultAsync();
        protected abstract ProjectionDefinition<TEntity> GetProjectionDefinition();

        protected abstract FilterDefinition<TEntity> GetReferenceEntityFilter(TEntity validatedEntity);

        protected abstract FilterDefinition<TEntity> GetValidatedEntityFilterDefinition(Guid companyId);

        protected virtual Func<TEntity, Task> Processor(Action<ValidationResult> validationCallback)
        {
            return async validatedEntity =>
            {
                var referenceEntity = await GetReferenceElementAsync(validatedEntity).ConfigureAwait(false);

                if (referenceEntity != null)
                {
                    var validationResult =
                        await Validator.ValidateAsync(validatedEntity, referenceEntity).ConfigureAwait(false);

                    validationCallback.Invoke(validationResult);
                }
            };
        }
    }
}
